import { Component, OnInit, ElementRef, Renderer, OnChanges, EventEmitter, AfterViewInit, ViewChild } from '@angular/core';
import { Observable } from "rxjs/Observable";
import { Subject } from "rxjs/Subject";

export const setStyle = (elem: HTMLElement, name: string | {}, value?) => {
  if (typeof name === "string") {
    elem.style[name] = value;
  } else if (typeof name === "object") {
    Object.keys(name).forEach(prop => elem.style[prop] = name[prop]);
  }
}

export const getLookingOfElem = (elem: HTMLElement) => elem.getBoundingClientRect();

const defaultParams = {
  outerWidth: "5px",
  outerHeight: "10px",
}
@Component({
  // moduleId: module.id,
  selector: 'cm-scroll-bar',
  templateUrl: 'scroll-bar.component.html',
  inputs: [
    "elem",
    "isVertical",
    "clickStep",
    "wheelStep",
    "top",
    "bottom",
    "initWithBottom",
    "scrollBarSource",
    "resetSource"
  ],
  styleUrls: [
    'scroll-bar.component.less'
  ],
  outputs: [
    "onScroll",
  ],
})
export class CmScrollBar implements OnInit, AfterViewInit {
  private top;
  private wheelStep = 1;
  private clickStep = 1;
  private isVertical = true; // 暂时只处理纵向
  private scrollBarSource: Observable<{ amountOfTotal: number; amountOfVisible: number }>;
  private resetSource: Observable<any>;
  private onScroll = new EventEmitter();
  // private stickBottom = true;
  private elem: HTMLElement;
  @ViewChild("outer", { read: ElementRef })
  private scrollOuter: HTMLElement;
  @ViewChild("bar", { read: ElementRef })
  private scrollBar: HTMLElement;

  private currentDataParams;

  constructor(elem: ElementRef,
    private renderer: Renderer
  ) { }
  ngOnInit() {

  }

  ngAfterViewInit() {
    let { renderer } = this;
    // FakeCode:
    // 1.读取配置属性 整体条目数量；视口条目容量；横滚动/竖滚动
    // 2.计算适当属性值 滚动滑块高(宽) / 滚动条 = 视口容量 / 整体数据数量； 视口容量 = 单条高(宽) / 视口高(宽)
    // 3.初始化状态仓库; 基本状态：当前滑块；当前滚动条；每一个项目保存位置和尺寸信息，通过getLookingOfElem函数获取
    // 4.仓库行为包括 GOTO:设置滑块相对位置 SET_CONTAINER:设置容器属性
    // 5.仓库订阅行为，发射Scroll事件，事件荷载为仓库更新后状态
    // 

    this.scrollOuter = this.scrollOuter["nativeElement"];
    this.scrollBar = this.scrollBar["nativeElement"];
    // this.scrollOuter.parentElement.style.position = "relative";
    this.scrollBarSource.subscribe(dataParams => {
      if (!dataParams) return;
      let lastParams = this.currentDataParams;
      let newTop;
      if (lastParams && !isNaN(parseFloat(this.scrollBar.style.top))) {
        newTop = Math.floor(parseFloat(this.scrollBar.style.top) / 100 * lastParams.amountOfTotal) / dataParams.amountOfTotal * 100 + "%";
      }
      this.currentDataParams = dataParams;
      setStyle(this.scrollOuter, {
        position: "absolute",
        top: this.top || 0,
        right: 0,
        width: this.isVertical ? defaultParams.outerWidth : "100%",
        bottom: this.isVertical ? 0 : defaultParams.outerHeight,
        visibility: dataParams.amountOfVisible >= dataParams.amountOfTotal ? "hidden" : "visible"
      });
      setStyle(this.scrollBar, {
        top: dataParams["sticky"] ? "unset" : dataParams["byContainer"] ? newTop : this.scrollBar.style.top,
        // top: this.stickBottom ? (1 - dataParams.amountOfVisible / dataParams.amountOfTotal) * 100 + "%" : this.scrollBar.style.top,
        bottom: dataParams["sticky"] ? 0 : "unset",
        left: this.scrollBar.style.left,
        width: this.isVertical ? "100%" : (dataParams.amountOfVisible / dataParams.amountOfTotal) * 100 + "%",
        height: this.isVertical ? (dataParams.amountOfVisible / dataParams.amountOfTotal) * 100 + "%" : "100%",
      });
    });

    let downSource = Observable.fromEvent(this.scrollBar, "mousedown");
    let upSource = Observable.fromEvent(document, "mouseup");
    let moveSource = Observable.fromEvent(document, "mousemove");

    let updateStyleSource = new Subject();
    // 最终滚动实现
    updateStyleSource.
      filter(e => (e["clientY"] || e["clientX"])).
      map((e: MouseEvent) => {
        // console.debug("Update: Source: ", e.clientY, getLookingOfElem(this.scrollOuter).top)
        return this.isVertical ? e.clientY - getLookingOfElem(this.scrollOuter).top : e.clientX - getLookingOfElem(this.scrollOuter).left
      }).
      map(destPos => this.getFixedPosition(destPos, this.scrollBar, this.scrollOuter)).
      distinctUntilChanged().
      subscribe(pos => {
        this.updateScrollPos(this.scrollOuter, this.scrollBar, pos)
      });
    // 滚动条拖拽
    downSource.
      mergeMap(e => moveSource.takeUntil(upSource)).
      subscribe(e => updateStyleSource.next(e));
    // 点击滚动条
    Observable.
      fromEvent(this.scrollOuter, "mousedown").
      filter((e: MouseEvent) => e.srcElement === this.scrollOuter).
      subscribe(e => updateStyleSource.next(e))


    let wheelSource =
      Observable.fromEvent(this.elem, "wheel").
        subscribe((e: WheelEvent) => {
          // console.error("Scroll Bar Wheel: ", e);
          let outerPos = getLookingOfElem(this.scrollOuter);
          let interPos = getLookingOfElem(this.scrollBar);
          if (interPos.bottom >= outerPos.bottom && e.deltaY > 0) return;

          updateStyleSource.next({
            // clientX: e.deltaX / Math.abs(e.deltaX) * this.wheelStep * (1 / this.currentDataParams["amountOfTotal"] * outerPos.width) + interPos.left,
            clientY: e.deltaY / Math.abs(e.deltaY) * this.wheelStep * (1 / this.currentDataParams["amountOfTotal"] * outerPos.height) + interPos.top,
          });
        });
    if (this.resetSource) {
      this.resetSource.subscribe(() => {
        setStyle(this.scrollBar, {
          top: 0,
          bottom: "unset",
          left: this.scrollBar.style.left,
          width: "100%",
          height: "100%",
        });
        this.updateScrollPos(this.scrollOuter, this.scrollBar, 0);
      });
    }

  }

  updateScrollPos(scrollOuter, scrollBar, fixedDestPos) {
    // console.debug("Update Scroll: ", scrollOuter, scrollBar, fixedDestPos);
    let property =
      fixedDestPos /
      (this.isVertical ?
        getLookingOfElem(scrollOuter).height :
        getLookingOfElem(scrollOuter).width);
    let barProperty =
      this.isVertical ?
        getLookingOfElem(scrollBar).height / getLookingOfElem(scrollOuter).height :
        getLookingOfElem(scrollBar).width / getLookingOfElem(scrollOuter).width;
    this.isVertical ? scrollBar.style.top = property * 100 + "%" : scrollBar.style.left = property * 100 + "%";
    // console.debug("Bottom: ", property)
    return this.onScroll.emit({
      sticky: property + barProperty === 1, // 是否附著在底部
      pos: property
    });
  }

  /**
   * 获取修正后纵坐标，避免溢出
   * 
   * @param {number} destPos 滑块的left或top属性值
   * @param {HTMLElement} elem 滑块元素
   * @param {HTMLElement} containerElem 容器元素
   * @param {boolean} [isVertical=true] 是否垂直滚动
   * @returns
   * 
   * @memberOf ScrollBarDirective
   */
  getFixedPosition(destPos: number, elem: HTMLElement, containerElem: HTMLElement, isVertical = true) {
    // console.debug("FIXED: ", destPos)
    if (destPos < 0) {
      return 0;
    }
    if (isVertical && destPos + getLookingOfElem(elem).height > getLookingOfElem(containerElem).height) {
      return getLookingOfElem(containerElem).height - getLookingOfElem(elem).height;
    }
    if (!isVertical && destPos + getLookingOfElem(elem).width > getLookingOfElem(containerElem).width) {
      return getLookingOfElem(containerElem).width - getLookingOfElem(elem).width;
    }
    return destPos;
  }
}